//----------------------------------------------
//            NGUI: Next-Gen UI kit
// Copyright © 2011-2014 Tasharen Entertainment
//----------------------------------------------
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEngine;


namespace Hugula {

    /// <summary>
    /// MemoryStream.ReadLine has an interesting oddity: it doesn't always advance the stream's position by the correct amount:
    /// http://social.msdn.microsoft.com/Forums/en-AU/Vsexpressvcs/thread/b8f7837b-e396-494e-88e1-30547fcf385f
    /// Solution? Custom line reader with the added benefit of not having to use streams at all.
    /// </summary>

    public class ByteReader {
        byte[] mBuffer;
        int mOffset = 0;

        public ByteReader (byte[] bytes) { mBuffer = bytes; }
        public ByteReader (TextAsset asset) { mBuffer = asset.bytes; }

        /// <summary>
        /// Read the contents of the specified file and return a Byte Reader to work with.
        /// </summary>

        static public ByteReader Open (string path) {
#if UNITY_EDITOR || (!UNITY_FLASH && !NETFX_CORE && !UNITY_WP8)
            FileStream fs = File.OpenRead (path);

            if (fs != null) {
                fs.Seek (0, SeekOrigin.End);
                byte[] buffer = new byte[fs.Position];
                fs.Seek (0, SeekOrigin.Begin);
                fs.Read (buffer, 0, buffer.Length);
                fs.Close ();
                return new ByteReader (buffer);
            }
#endif
            return null;
        }

        /// <summary>
        /// Whether the buffer is readable.
        /// </summary>

        public bool canRead { get { return (mBuffer != null && mOffset < mBuffer.Length); } }

        /// <summary>
        /// Read a single line from the buffer.
        /// </summary>

        static string ReadLine (byte[] buffer, int start, int count) {
#if UNITY_FLASH
            // Encoding.UTF8 is not supported in Flash :(
            StringBuilder sb = new StringBuilder ();

            int max = start + count;

            for (int i = start; i < max; ++i) {
                byte byte0 = buffer[i];

                if ((byte0 & 128) == 0) {
                    // If an UCS fits 7 bits, its coded as 0xxxxxxx. This makes ASCII character represented by themselves
                    sb.Append ((char) byte0);
                } else if ((byte0 & 224) == 192) {
                    // If an UCS fits 11 bits, it is coded as 110xxxxx 10xxxxxx
                    if (++i == count) break;
                    byte byte1 = buffer[i];
                    int ch = (byte0 & 31) << 6;
                    ch |= (byte1 & 63);
                    sb.Append ((char) ch);
                } else if ((byte0 & 240) == 224) {
                    // If an UCS fits 16 bits, it is coded as 1110xxxx 10xxxxxx 10xxxxxx
                    if (++i == count) break;
                    byte byte1 = buffer[i];
                    if (++i == count) break;
                    byte byte2 = buffer[i];

                    if (byte0 == 0xEF && byte1 == 0xBB && byte2 == 0xBF) {
                        // Byte Order Mark -- generally the first 3 bytes in a Windows-saved UTF-8 file. Skip it.
                    } else {
                        int ch = (byte0 & 15) << 12;
                        ch |= (byte1 & 63) << 6;
                        ch |= (byte2 & 63);
                        sb.Append ((char) ch);
                    }
                } else if ((byte0 & 248) == 240) {
                    // If an UCS fits 21 bits, it is coded as 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 
                    if (++i == count) break;
                    byte byte1 = buffer[i];
                    if (++i == count) break;
                    byte byte2 = buffer[i];
                    if (++i == count) break;
                    byte byte3 = buffer[i];

                    int ch = (byte0 & 7) << 18;
                    ch |= (byte1 & 63) << 12;
                    ch |= (byte2 & 63) << 6;
                    ch |= (byte3 & 63);
                    sb.Append ((char) ch);
                }
            }
            return sb.ToString ();
#else
            return Encoding.UTF8.GetString (buffer, start, count);
#endif
        }

        /// <summary>
        /// Read a single line from the buffer.
        /// </summary>

        public string ReadLine () { return ReadLine (true); }

        /// <summary>
        /// Read a single line from the buffer.
        /// </summary>

        public string ReadLine (bool skipEmptyLines) {
            int max = mBuffer.Length;

            // Skip empty characters
            if (skipEmptyLines) {
                while (mOffset < max && mBuffer[mOffset] < 32) ++mOffset;
            }

            int end = mOffset;

            if (end < max) {
                for (;;) {
                    if (end < max) {
                        int ch = mBuffer[end++];
                        if (ch != '\n' && ch != '\r') continue;
                    } else ++end;

                    string line = ReadLine (mBuffer, mOffset, end - mOffset - 1);
                    mOffset = end;
                    return line;
                }
            }
            mOffset = max;
            return null;
        }

        /// <summary>
        /// Assume that the entire file is a collection of key/value pairs.
        /// </summary>

        public Dictionary<string, string> ReadDictionary () {
            Dictionary<string, string> dict = new Dictionary<string, string> ();
            char[] separator = new char[] { '=' };

            while (canRead) {
                string line = ReadLine ();
                if (line == null) break;
                if (line.StartsWith ("//")) continue;

#if UNITY_FLASH
                string[] split = line.Split (separator, System.StringSplitOptions.RemoveEmptyEntries);
#else
                string[] split = line.Split (separator, 2, System.StringSplitOptions.RemoveEmptyEntries);
#endif

                if (split.Length == 2) {
                    string key = split[0].Trim ();
                    string val = split[1].Trim ().Replace ("\\n", "\n");
                    dict[key] = val;
                }
            }
            return dict;
        }

        static List<string> mTemp = new List<string> ();

        /// <summary>
        /// Read a single line of Comma-Separated Values from the file.
        /// </summary>

        public List<string> ReadCSV () {
            mTemp.Clear ();
            string line = "";
            bool insideQuotes = false;
            int wordStart = 0;

            while (canRead) {
                if (insideQuotes) {
                    string s = ReadLine (false);
                    if (s == null) return null;
                    s = s.Replace ("\\n", "\n");
                    line += "\n" + s;
                    ++wordStart;
                } else {
                    line = ReadLine (true);
                    if (line == null) return null;
                    line = line.Replace ("\\n", "\n");
                    wordStart = 0;
                }

                for (int i = wordStart, imax = line.Length; i < imax; ++i) {
                    char ch = line[i];

                    if (ch == ',') {
                        if (!insideQuotes) {
                            mTemp.Add (line.Substring (wordStart, i - wordStart));
                            wordStart = i + 1;
                        }
                    } else if (ch == '"') {
                        if (insideQuotes) {
                            if (i + 1 >= imax) {
                                mTemp.Add (line.Substring (wordStart, i - wordStart).Replace ("\"\"", "\""));
                                return mTemp;
                            }

                            if (line[i + 1] != '"') {
                                mTemp.Add (line.Substring (wordStart, i - wordStart));
                                insideQuotes = false;

                                if (line[i + 1] == ',') {
                                    ++i;
                                    wordStart = i + 1;
                                }
                            } else ++i;
                        } else {
                            wordStart = i + 1;
                            insideQuotes = true;
                        }
                    }
                }

                if (wordStart < line.Length) {
                    if (insideQuotes) continue;
                    mTemp.Add (line.Substring (wordStart, line.Length - wordStart));
                }
                return mTemp;
            }
            return null;
        }
    }
}